/**
* @file diversifyKAUTH.c
*
* @brief Example key diversification implementation
*
* Copyright � 2016 HID Corporation.  All rights reserved.
*
* This software is protected by copyright law and international treaties.
* Any unauthorized reproduction, distribution or use of the software is prohibited.
*
*/

#include <stddef.h>

#include "defines.h"
#include "utils.h"
#include "diversifyKAUTH.h"
#include "modeOMAC.h"

#include "Log.h"

/// <summary>
/// This is the main function that performs the KAUTH key diversification. 
/// </summary>
/// <remarks>
/// This implementation optionally calls functions to allocate / free sections of volatile memory.
/// These functions are not included in the sample code and must be provided externally, if volatile memory is going to be used.
/// To enable the calls, add the line "#define VMEM_SUPPORTED" to the defines.h file.
/// </remarks>
/// <param name="key"> pointer to the array containing the master K_AUTH key (must be 16 bytes long) </param>
/// <param name="oid"> pointer to the array containing the OID of the ADF </param>
/// <param name="oidLength"> length of the OID </param>
/// <param name="diversifier"> pointer to the array containing the diversifier (see separate instructions for obtaining this value) </param>
/// <param name="diversifierLength"> length of the diversifier </param>
/// <param name="algoInfoCipher"> identifier of the cipher algorithm (see separate instructions for obtaining this value) </param>
/// <param name="algoInfoHash"> identifier of the hash algorithm (see separate instructions for obtaining this value) </param>
/// <param name="keyNumber"> the Seos keyset number, also known as the Reference Data Qualifier </param>
/// <param name="divKENC"> pointer to the output array for the diversified K_ENC key </param>
/// <param name="divKMAC"> pointer to the output array for the diversified K_MAC key </param>
/// <param name="divKeyLength"> required diversified key length (for 3K3DES this will be '24', otherwise it will be '16') </param>
/// <returns> void </returns>
void diversifyKAUTH(uint8_t* key, uint8_t* oid, uint8_t oidLength, uint8_t* diversifier, uint8_t diversifierLength, uint8_t algoInfoCipher, uint8_t algoInfoHash, uint8_t keyNumber, uint8_t* divKENC, uint8_t* divKMAC, uint8_t divKeyLength)
{

    // Data derivation shall use KDF in counter mode as specified in NIST SP 800-108. 
    // The PRF used in the KDF shall be CMAC as specified in NIST SP 800-38B, used with full 16 byte output length. 
    // The "fixed input data" plus iteration counter shall be the concatenation of the following items 
    // (note that NIST SP 800-108 allows the reordering of input data fields as long as the order, 
    // coding and length of each field is unambiguously defined):

    // A 12 byte "label" consisting of 11 bytes with value '00' followed by a one byte derivation constant as defined below.
    // A one byte "separation indicator" with value '00'. 
    // A 2 byte integer "L" specifying the length in bits of the derived data (value '0040', '0080', '00C0' or '0100').
    // A 1 byte counter "i" as specified in the KDF (which may take the values '01' or '02'; value '02' is used when "L" takes the values '00C0' and '0100', i.e. when the PRF of the KDF is to be called twice to generate enough derived data).
    // The "context" parameter of the KDF. Its content is further specified in the sections below applying the data derivation scheme.

    // Z[0]..Z[10] - 11 label '00' bytes
    // Z[11] - derivation constant:
    //           '04' - for K_ENC
    //           '06' - for K_MAC 
    // Z[12] - separation indicator
    // Z[13]..Z[14] - L (MSB,LSB)
    // Z[15] - i
    // Z[16] - context, algoInfo Cipher:
    //           �02� - 2-Key Triple DES (2K3DES) in CBC Mode
    //           �04� - 3-Key Triple DES (3K3DES) in CBC Mode 
    //           �09� - AES-128 in CBC mode
    // Z[17] - context, algoInfo Hash:
    //           �06� - SHA-1 (hash assigned to RSA-1024)
    //           �07� - SHA-256 (hash assigned to RSA-2048)
    // Z[18] - context, key number
    // Z[19]... - context, OID and diversification data

#ifdef VMEM_SUPPORTED
    ModeOMACContext* ctx;    
    uint8_t* kdfContext;
	uint8_t* divKey;
    uint16_t keys;

    ctx = (ModeOMACContext*)vmem_alloc(sizeof(ModeOMACContext));
    kdfContext = vmem_alloc(19 + oidLength + diversifierLength);
	divKey = vmem_alloc(ALGOAES_BLOCK_SIZE);
#else
	ModeOMACContext OMACctx;
	uint8_t kdfContext[64];
	uint8_t ctxKey[16];
	uint8_t divKey[16];
	uint16_t keys;

	ModeOMACContext* ctx = &OMACctx;
	ctx->key = ctxKey;
#endif
	utils_fill(kdfContext, 19 + oidLength + diversifierLength, 0x00);

    kdfContext[14] = (uint8_t)(divKeyLength << 3); // LSB of L: the length is in bits
    kdfContext[16] = algoInfoCipher;
    kdfContext[17] = algoInfoHash;
    kdfContext[18] = keyNumber;

    utils_copy(oid, &kdfContext[19], oidLength);
    utils_copy(diversifier, &kdfContext[19 + oidLength], diversifierLength);


    // diversify for the ENC key
    kdfContext[11] = 0x04; // derivationConstant for K_ENC
    kdfContext[15] = 1;    // initialize counter "i"
	LOG_SBUF("kdfContext", kdfContext, (19 + oidLength + diversifierLength));
    for (keys = 0; keys < divKeyLength; keys += ALGOAES_BLOCK_SIZE)
    {
        modeOMAC_init(ctx, (AlgoSymmetric)&algoAES_process, ALGOAES_BLOCK_SIZE, key, ALGOAES_KEY_SIZE);
        modeOMAC_finish(ctx, kdfContext, 19 + oidLength + diversifierLength);
        modeOMAC_getResult(ctx, divKey, ALGOAES_BLOCK_SIZE);

		utils_copy(divKey, &divKENC[keys], (ALGOAES_BLOCK_SIZE >= (divKeyLength - keys))?  ALGOAES_BLOCK_SIZE : (divKeyLength - keys));
        
	    kdfContext[15]++; // increment "i"
    }

    // diversify for the MAC key
    kdfContext[11] = 0x06; // derivationConstant for K_MAC;
    kdfContext[15] = 1;    // re-initialize counter "i"


    for (keys = 0; keys < divKeyLength; keys += ALGOAES_BLOCK_SIZE)
    {
        modeOMAC_init(ctx, (AlgoSymmetric)&algoAES_process, ALGOAES_BLOCK_SIZE, key, ALGOAES_KEY_SIZE);
        modeOMAC_finish(ctx, kdfContext, 19 + oidLength + diversifierLength);
        modeOMAC_getResult(ctx, divKey, ALGOAES_BLOCK_SIZE);

		utils_copy(divKey, &divKMAC[keys], (ALGOAES_BLOCK_SIZE >= (divKeyLength - keys))?  ALGOAES_BLOCK_SIZE : (divKeyLength - keys));

	    kdfContext[15]++; // increment "i"
	}


#ifdef VMEM_SUPPORTED
    vmem_free((void**)&divKey);
    vmem_free((void**)&kdfContext);
    vmem_free((void**)&ctx);                                    
#endif
}
